package data;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Text;

public class AggregatedEdgeMap extends TreeMap<Location, AggregatedEdge> {

	/**
	 * 
	 */
	private static final long serialVersionUID = -3639834160757560441L;

	public static final int OUTPUT_EXT = 10;
	
	private static final int OUTPUT_GPX = 5;
	
	private static final int DISTANCE_EDGE_NEAREST = 1;
	
	private static final int DISTANCE_LOC = 2;
	
	private static final int DISTANCE_EDGE_FAREST = 3;

	private static final int DISTANCE_EDGE_AVERAGE = 4;

	private static final int STANDARD_DISTANCE = DISTANCE_EDGE_AVERAGE;
	
	private static final double RADIUS_MULTIPLICATOR = 500.0;
	
	private static final int MAX_NEAR = 3;

	private static Double precision = 100000000.0;
	
	private int currentDistance;
	
	private TreeMap<Location, AggregatedEdge> mirroredTo = new TreeMap<Location, AggregatedEdge>();
	
	private TempEdgeMap temporaryMap = null;
	
	public AggregatedEdgeMap() {
		
		this.currentDistance = STANDARD_DISTANCE;

	}

	public void addTrace(EdgeMap em) {

		temporaryMap = new TempEdgeMap();
		
		mergeTrace(em);
		//TODO: tempMap verarbeiten. beforeMerged eintragen. mergeDistance ermitteln. 

		EdgeMap secondMap = new EdgeMap();
		
		secondMap.putAll(temporaryMap);
		
		temporaryMap = new TempEdgeMap();
		
		mergeTrace(secondMap);

		Iterator<Location> tempIt = temporaryMap.descendingKeySet().iterator();
		while (tempIt.hasNext()){
			insertEdge(em, tempIt.next());
		}

		
		
		
		
	}

	private void mergeTrace(EdgeMap em) {
		Iterator<Location> emIt = em.descendingKeySet().iterator();
		while (emIt.hasNext()) {
			Location loc = emIt.next();
			Edge ed = em.get(loc);
			ArrayList<Integer> nc = loc.getNearClusters();
			Location hi = this.higherKey(loc);
			Location low = this.lowerKey(loc);
			if (hi == null && low == null) {
				insertEdge(em, loc);
			} else if (hi == null) {

				inspectEdge(em, loc, nc, low, false);

			} else if (low == null) {

				inspectEdge(em, loc, nc, hi, true);

			} else {

				int clusterHi = hi.getCluster();
				int clusterLow = low.getCluster();
				if (nc.contains(clusterHi) == false
						&& nc.contains(clusterLow) == false) {
					insertEdge(em, loc);

				} else if (nc.contains(clusterHi) == false) {

					inspectEdge(em, loc, nc, low, false);

				} else if (nc.contains(clusterLow) == false) {

					inspectEdge(em, loc, nc, hi, true);

				} else {

					inspectBothEdges(em, loc, nc, hi, low);

				}
			}

		}
	}

	private void inspectBothEdges(EdgeMap em, Location loc,
			ArrayList<Integer> nc, Location hi, Location low) {

		// test for lat diff -> if positive also other lower edges may be
		// interesting
		while (!this.containsKey(hi)){
			hi = this.higherKey(hi);
		}
		while (!this.containsKey(low)){
			low = this.lowerKey(low);
		}

		double latDiffHi = Math.abs(loc.getLatitude() - hi.getLatitude());
		double latDiffLow = Math.abs(loc.getLatitude() - low.getLatitude());
		AggregatedEdge aggEdHi = this.get(hi);
		AggregatedEdge aggEdLow = this.get(low);
		if (aggEdHi.getTreshold() < latDiffHi && aggEdLow.getTreshold() < latDiffLow) {
			noNearEdges(em,loc);
		} else {
			// fetch all higher and lower keys with lat diff smaller than
			// treshold
			ArrayList<Location> nearStarts = new ArrayList<Location>();
			while (aggEdHi.getTreshold() >= latDiffHi) {
				if (this.containsKey(hi)){
					nearStarts.add(hi);
				}
				if (this.higherKey(hi) != null) {
					hi = this.higherKey(hi);
					latDiffHi = Math.abs(loc.getLatitude() - hi.getLatitude());
				} else {
					break;
				}
			}
			while (aggEdLow.getTreshold() >= latDiffLow) {
				if (this.containsKey(low)){
					nearStarts.add(low);
				}
				if (this.lowerKey(low) != null) {
					low = this.lowerKey(low);
					latDiffLow = Math.abs(loc.getLatitude() - low.getLatitude());
				} else {
					break;
				}
			}
			// find the one with the lowest "real" distance
			TreeMap<Double, Location> nearest = getNearest(em, loc, nearStarts);
			
			if (aggEdHi.getTreshold() < nearest.firstKey() && aggEdLow.getTreshold() < nearest.firstKey()) {
				noNearEdges(em,loc);
			} else {
				boolean merged = mergeTandA(em, loc, nearest);
				if (!merged){
					noNearEdges(em,loc);
				}
			}
		}
	}

	private void noNearEdges(EdgeMap em, Location loc) {
		insertToTemp(em, loc);
		//TODO: ersetzen: insertEdge(em, loc);
	}

	private void insertToTemp(EdgeMap em, Location loc) {
		// TODO Auto-generated method stub
		TempEdge tE = new TempEdge(em.get(loc));
		this.temporaryMap.put(loc, tE);
	}

	private TreeMap <Double, Location> getNearest(EdgeMap em, Location loc,
			ArrayList<Location> nearStarts) {
		Iterator<Location> nearIt = nearStarts.iterator();
		TreeMap<Double, Location> nearestTM = new TreeMap<Double, Location>();
		while (nearIt.hasNext()) {
			Location near = nearIt.next();
			if (nearestTM.size()<MAX_NEAR) {
				nearestTM.put(getDistance(em, loc, near), near);
			} else {
				Iterator<Location> nearestIt = nearestTM.values().iterator();
				boolean unmodified = true;
				while (nearestIt.hasNext()&&unmodified){
					Location nearCand = nearestIt.next();
					Location nearTest = getDistance(em, loc, nearCand, near);
					if (nearCand.compareTo(nearTest)!=0){
						nearestTM.remove(getDistance(em, loc, nearCand));
						nearestTM.put(getDistance(em, loc, nearTest), nearTest);
						unmodified = false;
					}
				}
			}
		}
		return nearestTM;
/*		Vector<Location> nearest = new Vector<Location>();
		Iterator<Double> distsIt = nearestTM.descendingKeySet().descendingIterator();
		while(distsIt.hasNext())
		{
			nearest.add(nearestTM.get(distsIt.next()));
		}
		
		return nearest;
*/	}

	private Double getDistance(EdgeMap em, Location loc, Location test){
		double testDist = 0.0;
		if (currentDistance == DISTANCE_EDGE_NEAREST){
			testDist = this.get(test).getNearestDistance(
				em.get(loc));
		}
		else if (currentDistance == DISTANCE_EDGE_FAREST){
			testDist = this.get(test).getFarestDistance(
					em.get(loc));
		}
		else if (currentDistance == DISTANCE_EDGE_AVERAGE){
			testDist = this.get(test).getAverageDistance(
					em.get(loc));
		}
		else if (currentDistance == DISTANCE_LOC){
			testDist = test.getDistanceTo(loc);
		}
		return testDist;
	}
	
	private Location getDistance(EdgeMap em, Location loc, Location nearest,
			Location test) {
		double testDist = 0.0;
		double nearDist = 0.0;
		if (currentDistance == DISTANCE_EDGE_NEAREST){
			testDist = this.get(test).getNearestDistance(
				em.get(loc));
			nearDist = this.get(nearest).getNearestDistance(
				em.get(loc));
		}
		else if (currentDistance == DISTANCE_EDGE_FAREST){
			testDist = this.get(test).getFarestDistance(
					em.get(loc));
			nearDist = this.get(nearest).getFarestDistance(
					em.get(loc));
		}
		else if (currentDistance == DISTANCE_EDGE_AVERAGE){
			testDist = this.get(test).getAverageDistance(
					em.get(loc));
			nearDist = this.get(nearest).getAverageDistance(
					em.get(loc));
		}
		else if (currentDistance == DISTANCE_LOC){
			testDist = test.getDistanceTo(loc);
			nearDist = nearest.getDistanceTo(loc);
		}
		if (testDist < nearDist) {
			nearest = test;
		}
		return nearest;
	}

	private void insertEdge(EdgeMap em, Location loc) {
		AggregatedEdge newAgg = new AggregatedEdge();
		newAgg.deriveFromEdge(em.get(loc));
		this.put(loc, newAgg);
	}

	private void inspectEdge(EdgeMap em, Location loc, ArrayList<Integer> nc,
			Location hi, boolean high) {
		int cluster = hi.getCluster();
		if (nc.contains(cluster) == false) {
			insertEdge(em, loc);

		} else { // if it belongs to near clusters

			// test for lat diff -> if positive also other lower edges may be
			// interesting
			
			while (!this.containsKey(hi)){
				if (high){
					hi = this.higherKey(hi);
				}
				else{
					hi = this.lowerKey(hi);
				}
			}
			if (hi == null){
				return;
			}
			double latDiff = Math.abs(hi.getLatitude() - loc.getLatitude());
			AggregatedEdge aggEd = this.get(hi);
			if (aggEd.getTreshold() < latDiff) {
				noNearEdges(em,loc);
			} else {
				// fetch all higher keys with lat diff smaller than treshold
				ArrayList<Location> nearStarts = new ArrayList<Location>();
				while (aggEd.getTreshold() >= latDiff) {
					nearStarts.add(hi);
					Location nextKey = null;
					if (high) {
						nextKey = this.higherKey(hi);
					} else {
						nextKey = this.lowerKey(hi);
					}
					if (nextKey != null && this.containsKey(nextKey)) {
						hi = nextKey;
						latDiff = Math
								.abs(loc.getLatitude() - hi.getLatitude());
					} else {
						break;
					}
				}
				TreeMap<Double, Location> nearest = getNearest(em, loc, nearStarts);
				
				if (aggEd.getTreshold() < nearest.firstKey()) {
					noNearEdges(em,loc);
				} else {
					boolean merged = mergeTandA(em, loc, nearest);
					if (!merged){
						noNearEdges(em,loc);
					}
				}
			}
		}
	}

	private boolean mergeTandA(EdgeMap em, Location loc, TreeMap<Double, Location> nearest) {
		Iterator<Double> distsIt = nearest.keySet().iterator();
		boolean merged = false;
		while (distsIt.hasNext() && merged == false){
			Location near = nearest.get(distsIt.next());
			merged = mergeTandA(em, loc, near);
		}
		if (merged == false){
			//insertEdge(em, loc);
		}
		return merged;
	}
	
	private boolean mergeTandA(EdgeMap em, Location loc, Location nearest) {
		// the actual merging of aggregate and single trace
		// retrieve the gradient of the aggregate and the trace
		AggregatedEdge agg = this.get(nearest);
		if (agg==null){
			return false;
		}
		if (agg.getTreshold() < getDistance(em, loc, nearest)){
			return false;
		}
		double gradient = agg.getGradient();
		double interception = agg.getInterception();
		double interceptPerFrom = agg.getFrom().getLatitude()
				+ agg.getFrom().getLongitude() * (1 / gradient);
		double interceptPerTo = agg.getTo().getLatitude()
				+ agg.getTo().getLongitude() * (1 / gradient);
		Edge ed = em.get(loc);
		double gradEd = (ed.getFrom().getLatitude() - ed.getTo().getLatitude())
				/ (ed.getFrom().getLongitude() - ed.getTo().getLongitude());
		double interceptEd = ed.getFrom().getLatitude()
				- ed.getFrom().getLongitude() * gradEd;
		double interceptEdPerFrom = ed.getFrom().getLatitude()
				+ ed.getFrom().getLongitude() * (1 / gradEd);
		double interceptEdPerTo = ed.getTo().getLatitude()
				+ ed.getTo().getLongitude() * (1 / gradEd);

		// test for common point
		if (ed.getTo().compareTo(agg.getFrom())==0 || ed.getFrom().compareTo(agg.getTo())==0){
			return false;
		}

		// test for angle
		if (!sameDirection(agg, ed)){
			//insertEdge(em, loc);
			return false;
		}
		
		//TODO: test for average angle
		if (!isWithinAngle(agg, ed, 30)){
			//insertEdge(em, loc);
			return false;
		}
		
		int notWithin = 0;
		// slice of perpendicular (single trace) and edge (aggregation) with each other
		double sliceXFrom = (interceptEdPerFrom - interception)
				/ (gradient - (- 1 / gradEd));
		if (isSliceWithinEdge(agg, sliceXFrom)) {
			// slice is within edge
			if (compareDouble(sliceXFrom, agg.getFrom().getLongitude())==0) {
				//search for To Location to move analog
				AggregatedEdge to = searchToLocation(agg.getFrom());
				// move From location
				moveLocation(agg, ed.getFrom(), agg.getFrom());
				this.put(agg.getFrom(), agg);
				// move found To location
				if (to!=null){
					to.setTo(agg.getFrom());
					this.put(to.getFrom(), to);

				}

			} else if (compareDouble(sliceXFrom,agg.getTo().getLongitude())==0) {
				// search for From Location to move analog
				AggregatedEdge from = this.get(agg.getTo());
				// move To location
				moveLocation(agg, ed.getFrom(), agg.getTo());
				this.put(agg.getFrom(), agg);
				// move found From location
				if (from!=null){ //TODO: scheint immer null zu sein
					from.setFrom(agg.getTo());
					this.put(from.getFrom(), from);

				}
				else{
					// add new Edge
				    //insertEdge(em, loc);
				    return false;
				}
				
			} else {
				// split aggregation
				splitAggregation(agg, ed, gradEd, interceptEdPerFrom,
						sliceXFrom, ed.getFrom());

			}
			agg.setNumbers(agg.getNumbers() + 1);

		} else {
			notWithin++;
		}
			// slice not within aggregation
			double sliceXEdFrom = (interceptEd - interceptPerFrom)
					/ ((-1 / gradient) - gradEd);
			if (isSliceWithinEdge(ed, sliceXEdFrom)) {
				// slice within single trace
				//search for To Location to move analog
				AggregatedEdge to = searchToLocation(agg.getFrom());
				// move agg From along its prependicular
				moveAlongPerp(agg, gradient, interceptPerFrom, sliceXEdFrom, agg.getFrom());
				this.put(agg.getFrom(), agg);
				// move found To location
				if (to!=null){
					to.setTo(agg.getFrom());
					this.put(to.getFrom(), to);

				}

			}
			else{
				notWithin++;
			}

		
		double sliceXTo = (interceptEdPerTo - interception)
				/ (gradient - (-1 / gradEd));
		if (isSliceWithinEdge(agg, sliceXTo)) {
			// slice is within edge
			if (compareDouble(sliceXTo, agg.getFrom().getLongitude())==0) {
				//search for To Location to move analog
				AggregatedEdge to = searchToLocation(agg.getFrom());
				// move From location
				moveLocation(agg, ed.getTo(), agg.getFrom());
				this.put(agg.getFrom(), agg);
				// move found To location
				if (to!=null){
					to.setTo(agg.getFrom());
					this.put(to.getFrom(), to);
				}
				else{
					// add new Edge
					//insertEdge(em, loc);
					return false;
				}
			} else if (compareDouble(sliceXTo, agg.getTo().getLongitude())==0) {
				// search for From Location to move analog
				AggregatedEdge from = this.get(agg.getTo());
				// move To location
				moveLocation(agg, ed.getTo(), agg.getTo());
				this.put(agg.getFrom(), agg);
				// move found From location
				if (from!=null){
					from.setFrom(agg.getTo());
					this.put(from.getFrom(), from);
				}


			} else {
				// split aggregation
				splitAggregation(agg, ed, gradEd, interceptEdPerTo,
						sliceXTo, ed.getTo());
			}
			agg.setNumbers(agg.getNumbers() + 1);

		} else {
			notWithin++;
		}
			// slice not within aggregation
			double sliceXEdTo = (interceptEd - interceptPerTo)
					/ ((-1 / gradient) - gradEd);
			if (isSliceWithinEdge(ed, sliceXEdTo)) {
				// slice within single trace
				// search for From Location to move analog
				AggregatedEdge from = this.get(agg.getTo());
				// move agg To along its prependicular
				moveAlongPerp(agg, gradient, interceptPerTo, sliceXEdTo, agg.getTo());
				this.put(agg.getFrom(), agg);
				// move found From location
				if (from!=null){
					from.setFrom(agg.getTo());
					this.put(from.getFrom(), from);

				}

			}
			else{
				notWithin++;
			}

		
		if (notWithin >= 4){
			//insertEdge(em,loc);
			return false;
		}
		return true;
	}

	private boolean sameDirection(Edge ed1, Edge ed2) {
		return isWithinAngle(ed1, ed2, 90.0);
	}

	private boolean isWithinAngle(Edge ed1, Edge ed2, double criticalAngle) {
		double angle1 = ed1.getAngle();
		double angle2 = ed2.getAngle();
		double diff = Math.abs(angle1-angle2);
		if (diff > 180){
			diff = 360 - diff;
		}
		if (diff < criticalAngle){
			return true;
		}
		return false;
	}

	private void moveAlongPerp(AggregatedEdge agg, double gradient,
			double interceptPerMove, double sliceXEdMove, Location move) {
		double latSlice = interceptPerMove + (-1 / gradient) * sliceXEdMove;
		move.setLongitude((move.getLongitude() * agg.getNumbers()
				+ sliceXEdMove) / (agg.getNumbers() + 1));
		move.setLatitude((move.getLatitude() * agg.getNumbers() + latSlice)
				/ (agg.getNumbers() + 1));
		agg.setNumbers(agg.getNumbers() + 1);
	}

	private AggregatedEdge searchToLocation(Location from) {
		return mirroredTo.get(from);
	}
	
	private AggregatedEdge searchToLocation(Location from, Double radius) {
		//TODO: funktioniert noch nicht richtig. zweite TreeMap?
		radius = radius * RADIUS_MULTIPLICATOR;
		Location temp = new Location (from.getLatitude(), from.getLatitude());
		while (compareDouble(temp.getDistanceTo(from),radius)<=0){
			temp = this.higherKey(temp);
			AggregatedEdge ae = this.get(temp);
			if (ae.getTo().compareLL(from)==0){
				return ae;
			}
		}
		temp = new Location (from.getLatitude(), from.getLatitude());
		while (compareDouble(temp.getDistanceTo(from),radius)<=0){
			temp = this.lowerKey(temp);
			AggregatedEdge ae = this.get(temp);
			if (ae.getTo().compareLL(from)==0){
				return ae;
			}
		}
		
		return null;
	}

	private void moveLocation(AggregatedEdge agg, Location ed, Location move) {
		// along To Perpendicular
		move.setLongitude((move.getLongitude() * agg.getNumbers()
				+ ed.getLongitude()) / (agg.getNumbers() + 1));
		move.setLatitude((move.getLatitude() * agg.getNumbers()
				+ ed.getLatitude()) / (agg.getNumbers() + 1));
	}

	private boolean isSliceWithinEdge(Edge agg, double slice) {
		return ((compareDouble(agg.getFrom().getLongitude(),slice) <= 0) && (compareDouble(agg.getTo().getLongitude(), slice) >= 0 ))
				|| ((compareDouble(agg.getFrom().getLongitude(), slice) >= 0) && (compareDouble(agg.getTo().getLongitude(), slice)<= 0 ));
	}

	public static int compareDouble(double d1, double d2) {
		Double r1 = Math.round(d1 * precision) / precision;
		Double r2 = Math.round(d2 * precision) / precision;
		return (Double.compare(r1, r2));
	}

	private void splitAggregation(AggregatedEdge agg, Edge ed, double gradEd,
			double interceptEdPer, double sliceX, Location pull) {
		Location between = new Location();
		between.setLatitude(interceptEdPer + (-1 / gradEd)
				* sliceX);
		between.setLongitude((sliceX * agg.getNumbers()
				+ pull.getLongitude()) / (agg.getNumbers() + 1));
		between.setLatitude((between.getLatitude() * agg.getNumbers()
				+ pull.getLatitude()) / (agg.getNumbers() + 1));
		double splitLong = (agg.getTo().getLongitude() - sliceX) / (agg.getTo().getLongitude() - agg.getFrom().getLongitude()); 
		double way = agg.getNumbers() / (agg.getNumbers() + 1); 
		Location newFrom = new Location();
		
		Location newTo = new Location();
		
		AggregatedEdge first = new AggregatedEdge();
		first.cloneAggregatedEdge(agg);
		first.setTo(between);
		agg.setFrom(between);
		
		this.put(agg.getFrom(), agg);
		this.put(first.getFrom(), first);
		//%TODO: auch von und zu mit einbeziehen?!

	}
	
	public void outputAggregation(String filename){
		outputAggregation(filename, OUTPUT_EXT);
	}
	
	public void outputTrace(String filename){
		outputAggregation(filename, OUTPUT_GPX);
	}

	public void outputAggregation(String filename, int output){
		outputAggregation(filename, OUTPUT_GPX, 1);
	}

	public void outputAggregation(String filename, int output, int minNumber) {
		try {
			DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();
			DocumentBuilder docBuilder = dbfac.newDocumentBuilder();
			Document doc = docBuilder.newDocument();
			Element gpx = doc.createElement("gpx");
			doc.appendChild(gpx);
			Iterator<Location> keys = this.descendingKeySet().iterator();
			while (keys.hasNext()) {
				AggregatedEdge agg = this.get(keys.next());
				if (agg != null){
				if (minNumber <= agg.getNumbers()){
				Element trkseg = doc.createElement("trkseg");
				gpx.appendChild(trkseg);
				Element from = doc.createElement("trkpt");
				from.setAttribute("lat", String.valueOf(agg.getFrom()
						.getLatitude()));
				from.setAttribute("lon", String.valueOf(agg.getFrom()
						.getLongitude()));
				trkseg.appendChild(from);
				Element eleFrom = doc.createElement("ele");
				trkseg.appendChild(eleFrom);
				Text elevationFrom = doc.createTextNode(String.valueOf(agg
						.getFrom().getElevation()));
				eleFrom.appendChild(elevationFrom);
				trkseg.appendChild(eleFrom);
				Element to = doc.createElement("trkpt");
				to.setAttribute("lat", String
						.valueOf(agg.getTo().getLatitude()));
				to.setAttribute("lon", String.valueOf(agg.getTo()
						.getLongitude()));
				trkseg.appendChild(to);
				Element eleTo = doc.createElement("ele");
				trkseg.appendChild(eleTo);
				Text elevationTo = doc.createTextNode(String.valueOf(agg
						.getTo().getElevation()));
				eleTo.appendChild(elevationTo);
				trkseg.appendChild(eleTo);
				if (output >= OUTPUT_EXT){
					Element diff = doc.createElement("timediff");
					trkseg.appendChild(diff);
					Text difference = doc.createTextNode(String.valueOf(agg
						.getDistance()));
					diff.appendChild(difference);
					Element number = doc.createElement("number");
					trkseg.appendChild(number);
					Text numbers = doc.createTextNode(String.valueOf(agg.getNumbers()));
					number.appendChild(numbers);
				}
				}
				}
			}
			// ///////////////
			// Output the XML

			// set up a transformer
			TransformerFactory transfac = TransformerFactory.newInstance();
			Transformer trans = transfac.newTransformer();
			trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
			trans.setOutputProperty(OutputKeys.INDENT, "yes");

			// create string from xml tree
			StringWriter sw = new StringWriter();
			StreamResult result = new StreamResult(sw);
			DOMSource source = new DOMSource(doc);
			trans.transform(source, result);
			String xmlString = sw.toString();

		    BufferedWriter out = new BufferedWriter(new FileWriter(filename));
		    out.write(xmlString);
		    out.close();

		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}
	
	public AggregatedEdge put(Location key, AggregatedEdge value){
		this.mirroredTo.put(value.getTo(), value);
		return super.put(key, value);
	}

}
